package com.inyourcode.core.db;

import com.inyourcode.core.db.api.IDBResource;
import com.inyourcode.core.db.api.IDataContext;
import com.inyourcode.core.db.iml.MutilpleResource;
import com.inyourcode.core.db.iml.RedisResource;
import com.inyourcode.core.serialization.api.Serializer;
import com.inyourcode.core.serialization.api.SerializerFactory;
import com.inyourcode.core.serialization.api.SerializerType;
import com.inyourcode.core.threads.ConsumerTask;
import com.inyourcode.core.threads.api.HashExecutor;
import com.inyourcode.core.transport.session.api.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * //TODO 数据存储失败策略
 *
 * @author JackLei
 */
public class BasicDataContext implements IDataContext {
    private static final Logger LOGGER = LoggerFactory.getLogger(BasicDataContext.class);
    public static final String PLAYER_DATA_KEY = "player:";
    public static final int STATE_INIT = 1;
    public static final int STATE_LOADING = 2;
    public static final int STATE_LOADED = 3;
    public static final int STATE_ERROR = 4;
    private static Serializer serializer = SerializerFactory.getSerializer(SerializerType.PROTO_STUFF.value());
    //后续看下怎么优化，不用线程安全的map
    protected Map<Class, Object> dataMap = new ConcurrentHashMap<>();
    protected Map<String, EntityWrapper> StructMapByKey = new HashMap<>();
    protected Map<Class, EntityWrapper> structMapByClass = new HashMap<>();
    private Long id;
    private volatile int state = STATE_INIT;
    private IDBResource dbResource;
    private AtomicInteger saveCount = new AtomicInteger(0);

    public BasicDataContext(Long id, IDBResource dbResource, List<EntityWrapper> wrapperList) {
        this.id = id;
        this.dbResource = dbResource;
        for (EntityWrapper entityWrapper : wrapperList) {
            StructMapByKey.put(entityWrapper.getKey(), entityWrapper);
            structMapByClass.put(entityWrapper.getClazz(), entityWrapper);
        }
    }

    @Override
    public Long id() {
        return id;
    }

    @Override
    public void loadAll() {
        if (state() != STATE_INIT) {
            return;
        }

        this.state = STATE_LOADING;

        Map<String, byte[]> dataFromRedis = this.dbResource.load(dataKey());
        if (dataFromRedis == null) {
            this.state = STATE_LOADED;
            return;
        }

        for (Map.Entry<String, byte[]> entry : dataFromRedis.entrySet()) {
            String key = entry.getKey();
            byte[] data = entry.getValue();

            EntityWrapper entityWrapper = this.StructMapByKey.get(key);
            if (entityWrapper == null) {
                this.state = STATE_ERROR;
                LOGGER.error("The data faild to load, struct class is null, key:{}", key);
                return;
            }

            Class entityClazz = entityWrapper.getClazz();
            Object readObject = serializer.readObject(data, entityClazz);
            this.dataMap.put(entityClazz, readObject);
        }
        this.state = STATE_LOADED;
    }

    @Override
    public void saveAll(HashExecutor dbExecutor, Session session, ConsumerTask callBack){
        Map<String, byte[]> dirtyDataMap = dirtyData();
        dbExecutor.crossExecute(id().hashCode(),
                session,
                () -> {
                    dbResource.save(dataKey(), dirtyDataMap);
                    LOGGER.info("Data storage succeeded, sessionId:{}, saveCount:{}", session.getId(), saveCount.incrementAndGet());
                },
                callBack
        );

    }

    @Override
    public void updateData(Object data) {
        EntityWrapper entityWrapper = this.structMapByClass.get(data.getClass());
        if (entityWrapper == null) {
            //TODO 失败处理策略
            return;
        }

        Class entityClazz = entityWrapper.getClazz();
        this.dataMap.put(entityClazz, data);

        byte[] bytes = serializer.writeObject(data);

        Map<String, byte[]> dataMap = new HashMap<>();
        dataMap.put(entityWrapper.getKey(), bytes);
        this.dbResource.save(dataKey(), dataMap);
    }

    @Override
    public <T> T getData(Class<T> type) {
        Object data = dataMap.get(type);
        if (data == null) {
            return null;
        }
        return (T) data;
    }

    @Override
    public void putData(Object data) {
        EntityWrapper entityWrapper = this.structMapByClass.get(data.getClass());
        if (entityWrapper == null) {
            //TODO 失败处理策略
            return;
        }

        this.dataMap.put(entityWrapper.getClazz(), data);
    }

    @Override
    public String dataKey() {
        return PLAYER_DATA_KEY + id;
    }

    @Override
    public int state() {
        return state;
    }

    @Override
    public void cacheDataOptExpire(boolean clear, int expireTime) {
        if (this.dbResource instanceof MutilpleResource ) {
            ((MutilpleResource)this.dbResource).cacheDataOptExpire(clear, dataKey(), expireTime);
        } else if (this.dbResource instanceof RedisResource) {
            ((RedisResource)this.dbResource).cacheDataOptExpire(clear, dataKey(), expireTime);
        }
    }

    private Map<String, byte[]> dirtyData() {
        Map<String, byte[]> dirtyDataMap = new HashMap<>();
        Set<Map.Entry<Class, Object>> entries = this.dataMap.entrySet();

        for (Map.Entry<Class, Object> entry : entries) {
            Class clazz = entry.getKey();
            Object data = entry.getValue();
            EntityWrapper entityWrapper = this.structMapByClass.get(clazz);
            byte[] bytes = serializer.writeObject(data);
            dirtyDataMap.put(entityWrapper.getKey(), bytes);
        }

        return dirtyDataMap;
    }
}
